SQLite使用(一)&&选择表类型

      在SQLite中,主要有两种表类型,带rowid的表和不带rowid的表。我们利用create table 建一张表,默认都会有一个隐含名字为rowid的主键,暂且称带rowid的表为普通表。如果建表时指定 WITHOUT ROWID属性,那么建的表就是不带rowid的表。那么这两种表有什么区别?这篇文章主要讨论这两种表的存储实现,以及它们的优缺点和适用的应用场景。

1.rowid是什么?

      SQLite中rowid是一个隐身存储的列,8个字节存储,它有两个别名 _ROWID_ 和 OID,类型定义为INTEGER PRIMARY KEY,因此当用户建表时,某列定义为 INTEGER PRIMARY KEY,实质是rowid的别名。rowid是自增的,当该列插入null时, 会取当前表的最大值+1,作为该列的值。注意INTEGER与int不同,比如若列定义为 int primary key, 则插入null值,该列的值就是null,rowid依然会递增。查询可以通过select rowid from tablename得到rowid的值。为什么INTEGER与int不同,可以参考SQLite数据类型。 

2.AUTOINCREMENT属性

     在SQLite中,AUTOINCREMENT属性只能用于定义为INTEGER PRIMARY KEY的列,否则建表时会报错。没有AUTOINCREMENT属性的rowid始终取当前最大值+1,若删除了最大rowid所在的记录,导致这个rowid会重用。 采用AUTOINCREMENT属性可以避免重用情况,系统内部通过sqlite_sequence表来维护每个表的最大sequence值, 因此即使有删除情况,也不会导致rowid重用,严格单调递增,代价是执行插入时, 需要维护sqlite_sequence表,对性能有一定的损耗。 由于rowid采用8个字节存储,因此上限值为9223372036854775807,当超过这个值时,rowid属性会随机选择一个值, 只要不与表中已有记录冲突即可;而AUTOINCREMENT属性则会提示Error: database or disk is full。

3.存储区别

      普通表的PRIMRAY KEY实质是一个唯一索引,表数据按rowid组织(聚集索引), 通过主键访问表,实质需要访问唯一索引和聚簇索引,但对于INTEGER PRIMARY KEY除外, 它是rowid的一个别名,索引实质就是聚簇索引。在SQLite中,聚集索引采用B*树存储(B*树是B+的一个特例,非叶子节点间也通过双向指针相连),而普通索引(二级索引,唯一索引)采用B-树存储,B+树与B树的区别在于,B+树中非叶子节点只有key信息,叶子节点包含了key和value信息,并且叶子节点包含了所有key信息,key信息在叶子节点和非叶子节点存储了两遍,叶子节点间有双向指针相连;而B-树中,叶子节点和非叶子节点结构相同,都包含了key和value信息,查找可能在非叶子节点找到数据,直接返回。

      WITHOUT ROWID表采用B-Tree,叶子节点和非叶子节点都有记录所有内容, 因此若记录较长(超过page_size*1/20),扇出(节点记录数)很小,容易造成节点频繁分裂,不适合使用WITHOUT ROWID属性, 。WITHOUT ROWID 表只有一颗B-树,访问只需要访问一次B-树, 而普通表需要访问两次(索引+表),对于INTEGER PRIMARY KEY除外。WITHOUT ROWID不支持AUTOINCREMENT属性,并且PRIMARY KEY不能为null,普通表比较变态,PRIMARY KEY 属性列也可以为null(由于历史原因,没有修改)。

4. 例子

(1).普通表

CREATE TABLE IF NOT EXISTS wordcount1(
  word TEXT PRIMARY KEY,
  cnt INTEGER
);
wordcount1是一个普通表,底层采用两颗B树存储,一颗B*树存储的是表内容,key为rowid,value为(word,cnt);另一颗B-树是主键索引,key为(word,rowid)。因此对于每个word,都会在两颗B树中分别存一次。假设我们要查询word为"xyzzy"的记录:
SELECT cnt FROM wordcount1 WHERE word='xyzzy';

为了得到结果,首先需要通过主键索引找到rowid,然后再以rowid为key查找表,得到cnt,总共需要查找两次B树。

(2).WITHOUT ROWID表

CREATE TABLE IF NOT EXISTS wordcount2(
  word TEXT PRIMARY KEY,
  cnt INTEGER
) WITHOUT ROWID;
wordcount2是WITHOUT ROWID表,底层只有一颗B-树,即主键索引,相对于wordcount1表,wordcount2表中word只需存储一次。如果查询word为"xyzzy"的记录,只需查找一颗B树即可。因此在这种情况下,WITHOUT ROWID表不仅节省了存储,而且查询效率也比普通表效率高。

4.如何选择表类型?

1) 若主键为整型,采用普通表,将列定义为INTEGER PRIMARY KEY,这样保证只有只有1颗B*树,提高查询效率;
2) 若主键为非整型,记录比较小(不超过page_size*1/20),并且不依赖于rowid的逻辑序号,可以考虑使用WITHOUT ROWID表,节省空间 的同时,提高查询效率
3) 其它情况,则使用普通表,定义主键。

5.参考文档 

https://www.sqlite.org/withoutrowid.html

 

posted @ 2015-09-10 08:15  天士梦  阅读(2690)  评论(0编辑  收藏  举报